﻿using System;
namespace PoliticalClassifier.Support
{
    /// <summary>
    /// Range2D represents the range of the world coordinates across the plot.
    /// </summary>
    public struct Range2D
    {
        #region Construction
        /// <summary>
        /// Constructor.  Sets the initial range.
        /// </summary>
        /// <param name="xMin">The minimum x-coordinate in the range.</param>
        /// <param name="xMax">The maximum x-coordinate in the range.</param>
        /// <param name="yMin">The minimum y-coordinate in the range.</param>
        /// <param name="yMax">The maximum y-coordinate in the range.</param>
        public Range2D(double xMin, double xMax, double yMin, double yMax)
            : this()
        {
            XMin = xMin;
            XMax = xMax;
            YMin = yMin;
            YMax = yMax;
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets and sets the minimum x-coordinate in the range.
        /// </summary>
        public double XMin { get; set; }

        /// <summary>
        /// Gets and sets the maximum x-coordinate in the range.
        /// </summary>
        public double XMax { get; set; }

        /// <summary>
        /// Get and sets the minimum y-coordinate in the range.
        /// </summary>
        public double YMin { get; set; }

        /// <summary>
        /// Gets and sets the maximum y-coordinate in the range.
        /// </summary>
        public double YMax { get; set; }
        #endregion

        #region Public Methods
        /// <summary>
        /// Calculates the center point of the range.
        /// </summary>
        /// <returns>The center point of the range.</returns>
        public Point2D GetCenterPoint()
        {
            return new Point2D(XMin + GetHalfWidth(), YMin + GetHalfHeight());
        }

        /// <summary>
        /// Calculates the width of the range.
        /// </summary>
        /// <returns>The width of the range.</returns>
        public double GetWidth()
        {
            return XMax - XMin;
        }

        /// <summary>
        /// Calculates the height of the range.
        /// </summary>
        /// <returns>The height of the range.</returns>
        public double GetHeight()
        {
            return YMax - YMin;
        }

        /// <summary>
        /// Calculates the value of half of the width of the range.
        /// </summary>
        /// <returns>Half of the width of the range.</returns>
        public double GetHalfWidth()
        {
            return 0.5 * GetWidth();
        }

        /// <summary>
        /// Calculates the value of half of the height of the range.
        /// </summary>
        /// <returns>Half of the height of the range.</returns>
        public double GetHalfHeight()
        {
            return 0.5 * GetHeight();
        }

        /// <summary>
        /// Expand the range (if necessary) to make sure it includes 
        /// the given point.
        /// </summary>
        /// <param name="point">The point to include within the range.</param>
        public void ExpandToIncludePoint(Point2D point)
        {
            if (XMin > point.X)
                XMin = point.X;
            if (XMax < point.X)
                XMax = point.X;
            if (YMin > point.Y)
                YMin = point.Y;
            if (YMax < point.Y)
                YMax = point.Y;
        }

        /// <summary>
        /// Forces the range to fit [-1, 1] in both dimensions.
        /// </summary>
        /// <returns>The scale factor used as the divisor for normalizing the range.</returns>
        public double NormalizeRange()
        {
            RoundUpToNearestWholeNumber();
            double currentMagnitude = MaxMagnitude(XMin, XMax, YMin, YMax);

            // Let's not divide by zero.  In that case, everything is zero, so no need to scale.
            if (currentMagnitude == 0.0)
                currentMagnitude = 1.0;

            XMin /= currentMagnitude;
            XMax /= currentMagnitude;
            YMin /= currentMagnitude;
            YMax /= currentMagnitude;

            return currentMagnitude;
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Expand the range (if necessary) to make sure that all sides are 
        /// defined by a whole number with no fractional part.
        /// </summary>
        private void RoundUpToNearestWholeNumber()
        {
            XMin = Math.Floor(XMin);
            XMax = Math.Ceiling(XMax);
            YMin = Math.Floor(YMin);
            YMax = Math.Ceiling(YMax);
        }

        /// <summary>
        /// Calculates the maximum value of the absolute value of all given numbers.
        /// </summary>
        /// <param name="values">The numbers.</param>
        /// <returns>The maximum of the absolute value of the given numbers.</returns>
        private double MaxMagnitude(params double[] values)
        {
            if (values == null || values.Length == 0)
                return 0.0;

            double max = Math.Abs(values[0]);
            for (int i = 1; i < values.Length; ++i)
                max = Math.Max(max, Math.Abs(values[i]));

            return max;
        }
        #endregion
    }
}
